Skip to content

Leave the id out of an error response when the request id cannot be read - #615

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:omit_the_id_from_an_error_response_when_it_cannot_be_read
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:omit_the_id_from_an_error_response_when_it_cannot_be_read

Conversation

@koic

@koic koic commented Oct 15, 2026

Copy link
Copy Markdown
Member

Motivation and Context

An error response for a request whose id could not be read, such as a body that is not JSON, an id that is neither a string nor an integer, or a POST the transport refuses before parsing it, carries "id": null (issue #606). JSON-RPC 2.0 asks for that null, and MCP narrows it: the schema of 2025-11-25 and 2026-07-28 types the id of an error response as an optional string or integer, and the Streamable HTTP transport says the body of such a refusal may be a JSON-RPC error response that has no id. The TypeScript client refuses a null there at schema validation, and the conformance suite's wire-schema check reports it as a violation, except on a request the harness sent malformed on purpose.

JsonRpcHandler.error_response and the transport's error bodies now leave the id member out when there is no id to echo; every id that was echoed before is echoed as before, only the null is gone. The TypeScript and Python servers still answer with a null, and the Python client requires the member, so on a refused POST it replaces the server's message with its own generic error where it used to carry it.

Fixes #606.

How Has This Been Tested?

test/json_rpc_handler_test.rb, test/mcp/server_test.rb and the stdio and Streamable HTTP transport tests check that the member is absent, and that a refused initialize carrying the integer id 0 still echoes it. Against the previous library the absence checks fail on the present member.

Breaking Changes

An error response for a request whose id could not be read has no id member instead of "id": null. The Python client requires the member, so on a refused POST it reports its own generic error in place of the server's message; the TypeScript client, whose schema refuses a null there, now parses such a body.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

An error response for a request whose id could not be read, such as a body that is not JSON, an id that
is neither a string nor an integer, or a POST the transport refuses before parsing it, carries `"id": null` (issue modelcontextprotocol#606).
JSON-RPC 2.0 asks for that null, and MCP narrows it: the schema of 2025-11-25 and 2026-07-28 types the id of
an error response as an optional string or integer, and the Streamable HTTP transport says the body of such
a refusal may be a JSON-RPC error response that has no `id`. The TypeScript client refuses a null there at
schema validation, and the conformance suite's wire-schema check reports it as a violation, except on
a request the harness sent malformed on purpose.

`JsonRpcHandler.error_response` and the transport's error bodies now leave the `id` member out when there
is no id to echo; every id that was echoed before is echoed as before, only the null is gone. The TypeScript
and Python servers still answer with a null, and the Python client requires the member, so on a refused POST it
replaces the server's message with its own generic error where it used to carry it.

Fixes modelcontextprotocol#606.

## How Has This Been Tested?

`test/json_rpc_handler_test.rb`, `test/mcp/server_test.rb` and the stdio and Streamable HTTP transport tests check
that the member is absent, and that a refused `initialize` carrying the integer id 0 still echoes it.
Against the previous library the absence checks fail on the present member.

## Breaking Changes

An error response for a request whose id could not be read has no `id` member instead of `"id": null`.
The Python client requires the member, so on a refused POST it reports its own generic error in place of
the server's message; the TypeScript client, whose schema refuses a null there, now parses such a body.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error responses carry "id": null, which the MCP schema does not allow

2 participants